package com.basker.pisces.springcloud.log;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.StopWatch;

import com.alibaba.fastjson.JSON;
import com.basker.pisces.service.annotation.EnableAutoLog;
import com.basker.pisces.service.annotation.EnableAutoLog.AutoLogStrategy;
import com.basker.pisces.service.dto.IServiceResult;
import com.basker.pisces.springcloud.GlobalAspect;
import com.basker.pisces.springcloud.feign.FeignUtils;
import com.basker.pisces.utils.EnumUtils;

@Aspect
public class AutoLogInterceptor extends GlobalAspect {

    private static ThreadLocal<StopWatch> STOPWATCH_HOLDER = new NamedThreadLocal<>("AutoLogInterceptor's StopWatch");

    private static final Logger LOGGER = LoggerFactory.getLogger(AutoLogInterceptor.class);

    @After("@annotation(autoLog)")
    public void after(JoinPoint joinPoint, EnableAutoLog autoLog) {
        this.stopWatch();
    }

    @AfterReturning(pointcut = "@annotation(autoLog)", returning = "ret")
    public void afterReturning(JoinPoint joinPoint, EnableAutoLog autoLog, Object ret) {
        if (this.isMatch(autoLog, AutoLogStrategy.DebugAfterReturning)) {
            this.doDebugAfterReturning(joinPoint, autoLog, ret);
        } else if (this.isMatch(autoLog, AutoLogStrategy.ErrorAfterReturning)) {
            this.doErrorAfterReturning(joinPoint, autoLog, ret);
        }

        removeWatch();

    }

    @AfterThrowing(pointcut = "@annotation(autoLog)", throwing = "throwable")
    public void afterThrowing(JoinPoint joinPoint, EnableAutoLog autoLog, Throwable throwable) {
        if (this.isMatch(autoLog, AutoLogStrategy.ErrorAfterThrowing)) {
            this.doErrorAfterThrowing(joinPoint, autoLog, throwable);
        }

        removeWatch();

    }

    @Before("@annotation(autoLog)")
    public void before(JoinPoint joinPoint, EnableAutoLog autoLog) {
        this.startWatch();
    }

    private void doDebugAfterReturning(JoinPoint joinPoint, EnableAutoLog autoLog, Object ret) {
        Object[] args = joinPoint.getArgs();
        Object bodyArg = args.length > 0 ? args[0] : null;

        Logger logger = this.getLogger(joinPoint, autoLog);

        if (logger != null && logger.isDebugEnabled()) {
            String msg = getMessage(joinPoint, bodyArg, ret, null);
            logger.debug(msg);
        }
    }

    private void doErrorAfterReturning(JoinPoint joinPoint, EnableAutoLog autoLog, Object ret) {
        if (!(ret instanceof IServiceResult<?>)) {
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("return value's type is:" + (ret != null ? ret.getClass().getName() : "null")
                        + ",it's excepted a IServiceResult!");
            }

            return;
        }

        IServiceResult<?> serviceResult = (IServiceResult<?>) ret;
        if (serviceResult.isSuccess()) {
            // 正确响应不记录日志
            return;
        }

        Object[] args = joinPoint.getArgs();
        Object bodyArg = args.length > 0 ? args[0] : null;

        Logger logger = this.getLogger(joinPoint, autoLog);

        if (logger != null && logger.isErrorEnabled()) {
            String msg = getMessage(joinPoint, bodyArg, serviceResult, null);
            logger.error(msg);
        }
    }

    private void doErrorAfterThrowing(JoinPoint joinPoint, EnableAutoLog autoLog, Throwable throwable) {
        Object[] args = joinPoint.getArgs();
        Object bodyArg = args.length > 0 ? args[0] : null;

        Logger logger = this.getLogger(joinPoint, autoLog);

        if (logger != null && logger.isErrorEnabled()) {
            String msg = getMessage(joinPoint, bodyArg, null, throwable);
            logger.error(msg, throwable);
        }
    }

    private Logger getLogger(JoinPoint joinPoint, EnableAutoLog autoLog) {
        String logName = autoLog.logName();

        if (StringUtils.isEmpty(logName)) {
            Object target = joinPoint.getTarget();
            Class<?> feignClientType = FeignUtils.getFeignClientType(target);

            if (feignClientType != null) {
                logName = feignClientType.getName();
            } else {
                logName = target.getClass().getName();
            }

        }

        return LoggerFactory.getLogger(logName);
    }

    private String getMessage(JoinPoint joinPoint, Object arg, Object serviceResult, Throwable throwable) {
        StringBuffer msg = new StringBuffer();

        msg.append(joinPoint.getSignature().toShortString());
        msg.append("|");

        if (arg == null) {
            msg.append("{}");
        } else {
            msg.append(JSON.toJSONString(arg));
        }

        if (serviceResult != null) {
            msg.append("|");
            msg.append(JSON.toJSONString(serviceResult));
        }

        if (throwable != null) {
            msg.append("|");
            msg.append(throwable.getMessage());
        }

        StopWatch stopWatch = STOPWATCH_HOLDER.get();
        if (stopWatch != null) {
            msg.append("|");
            msg.append(stopWatch.getTotalTimeMillis());
        }

        return msg.toString();
    }

    /**
     * 判断autoLog配置的策略是否与指定的策略匹配
     *
     * @param autoLog
     * @param strategy
     * @return
     */
    private boolean isMatch(EnableAutoLog autoLog, AutoLogStrategy strategy) {
        int value = EnumUtils.getFlagEnumsValue(autoLog.strategy());
        return EnumUtils.isMatchEnum(value, strategy);
    }

    private void removeWatch() {
        STOPWATCH_HOLDER.remove();
    }

    private void startWatch() {
        StopWatch stopWatch = new StopWatch();
        STOPWATCH_HOLDER.set(stopWatch);

        stopWatch.start();
    }

    private void stopWatch() {
        StopWatch stopWatch = STOPWATCH_HOLDER.get();
        if (stopWatch == null) {
            return;
        }

        try {
            stopWatch.stop();
        } catch (IllegalStateException e) {
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("stopWatch occur an IllegalStateException:" + e.getMessage());
            }
        }
    }

}
